package dse.modules;

import java.util.Hashtable;
import java.util.Map;
import java.util.Random;

import testing.TestHelper;
import dse.DseException;
import dse.Network;
import dse.Node;
import dse.NodeState;
import dse.ExperimentConfiguration;
import dse.modules.eln.EarthLikeNetwork;

public class GeneralNodeEventScheduler implements NodeEventScheduler {
	private Random random;
	//private double periodLengthInSeconds;
	private double eventFrequencyPerSecond;
	private double startingUpPercent;
	private double eventsPerPeriod;
	
	public GeneralNodeEventScheduler () {/**/} 

	public void initalize (
			ExperimentConfiguration systemSettings,
			Map<String, String> moduleSettings
			)
	throws DseException
	{
		random = systemSettings.getRandom();
		//periodLengthInSeconds = systemSettings.getTimePeriodLength();
	
		ModuleUtil.SettingsReader sr = ModuleUtil.getSettingsReader(
				this.getClass(), moduleSettings
				);
		
		eventFrequencyPerSecond = sr.getDouble("frequency");
		startingUpPercent = sr.getDouble("starting-up-percent") / 100.0;
		
		eventsPerPeriod = eventFrequencyPerSecond;//periodLengthInSeconds * eventFrequencyPerSecond;
	}
	

	public void setInitalStates (Network network) {
		int nodesThatShouldBeDown = 
			(int)(network.nodeCount()-
					network.nodeCount()*startingUpPercent
					);
		
//		int snc = 0;
		for (Node n : network.nodes()) {
			if (n.isSupernode()){
//				snc++;
			}
		}
		
		while (nodesThatShouldBeDown > 0) {
			for (Node n : network.nodes()) {
				if (n.isSupernode())
					n.setState(NodeState.Up);
				else if (n.state() == NodeState.Down)
					continue;
				else if (nodesThatShouldBeDown == 0)
					n.setState(NodeState.Up);
				else {
					if (random.nextDouble() > startingUpPercent) {
						n.setState(NodeState.Down);
						nodesThatShouldBeDown--;
					} else
						n.setState(NodeState.Up);
				}				
			}
		}
		
		// Make sure it worked!
		nodesThatShouldBeDown = (int)
			(network.nodeCount() - 
					network.nodeCount()*startingUpPercent
					);
		int nodesThatAreDown = 0;
		for (Node n : network.nodes()) {
			assert(n.state() != NodeState.Invalid);
			if (n.state() == NodeState.Down)
				nodesThatAreDown++;
		}
		
		assert(nodesThatAreDown == nodesThatShouldBeDown);

	}

	public Map<Node, NodeState> getEvents (Network network) {
		Hashtable<Node, NodeState> events = new Hashtable<Node, NodeState>();
		
		if((int)eventsPerPeriod == 0)
		{
			return events;
		}
		for(Node n : network.nodes())
		{
			n.setState(NodeState.Up);
		}
		
		//int eventCount = random.nextInt((int)(2*eventsPerPeriod));
		int eventCount = (int)eventsPerPeriod;
		while (eventCount > 0) {
			eventCount--;
			
			int nodeId = random.nextInt(network.maxNodeId());
			Node node = network.lookupNode(nodeId+1);
			if (events.containsKey(node) || node.isSupernode())
				continue;
			if (node.state() == NodeState.Up)
				events.put(node, NodeState.Down);
			else
				events.put(node, NodeState.Up);
		}
		
		return events;
	}
	
	public int getEventsPerPeriod()
	{
		return (int)this.eventsPerPeriod;
	}
	
	
	/**
	 * For testing :)
	 * 
	 * @param args
	 * @throws Exception
	 */
	public static void main (String [] args) 
	throws Exception
	{
		if (args.length != 4 && args.length != 5) {
			System.out.println(
					"Usage: java -cp ... " +
					"dse.modules.GeneralNodeEventScheduler " +
					"<nodes> <frequency> <starting-up> " +
					"<rounds> (seed)"
					);
			System.exit(-1);
		}
		
		
		ExperimentConfiguration ss = TestHelper.createSystemSettings(args, 4);
		
		EarthLikeNetwork network = TestHelper.createELNetwork(
				ss, args, 0, -1, -1
				);
		
		TestHelper.createClusters(ss, args, -1, network);
		
		
		GeneralNodeEventScheduler gnes = new GeneralNodeEventScheduler ();
		Hashtable<String, String> props = new Hashtable<String, String>();
		props.put("frequency", args[1]);
		props.put("starting-up-percent", args[2]);
		
		gnes.initalize(ss, props);
		
		
		System.out.println("Setting inital states...");
		gnes.setInitalStates(network);
		
		int rounds = Integer.parseInt(args[3]);
		
		System.out.println("");
		for (int a = 0; a < rounds; a++) {
			System.out.println("Round #" + (a+1));
			
			Map<Node,NodeState> events = gnes.getEvents(network);
			int ec = 0;
			for (Node n : events.keySet()) {
				ec++;
				System.out.println(
						"" + n.id() + "\t" + n.state() + " -> " + 
						events.get(n)
						);
				// Actually change the state
				n.setState(events.get(n));
			}
			System.out.println("Event Count: " + ec);
			System.out.println("");
		}

		System.out.println("Done.");
	}
	
	
}
